package cn.xtits.xtf.common.db.sequence.dao;

import cn.xtits.xtf.common.db.sequence.SequenceException;
import cn.xtits.xtf.common.db.sequence.SequenceRange;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.*;

/**
 * Created by ShengHaiJiang on 16/4/15.
 */
public class DefaultSequenceDao implements SequenceDao {

    public static final Logger logger = LoggerFactory.getLogger(DefaultSequenceDao.class);


    public static final String DEFAULT_TABLE_SEQ_NAME = "t_sequence";
    public static final String DEFAULT_COLUMN_SEQ_NAME = "sequence_name";
    public static final String DEFAULT_COLUMN_SEQ_VALUE = "value";
    public static final String DEFAULT_COLUMN_SEQ_UPDATE_TIME = "update_time";
    public static final Integer DEFAULT_STEP = 1000;
    public static final Integer DEFAULT_RETRY_NUM = 200;

    public static final long DELTA = 100000000l;

    private String tableName = DEFAULT_TABLE_SEQ_NAME;
    private String columnSeqName = DEFAULT_COLUMN_SEQ_NAME;
    private String columnSeqValue = DEFAULT_COLUMN_SEQ_VALUE;
    private String columnUpdateTime = DEFAULT_COLUMN_SEQ_UPDATE_TIME;

    public static final Integer MIN_STEP = 1;
    public static final Integer MAX_STEP = 100000;

    private volatile String selectSql;
    private volatile String updateSql;
    private Object mutex = new Object();

    private DataSource dataSource;

    /** 步长 **/
    private int step = DEFAULT_STEP;
    /** 重试次数 **/
    private int retryNum = DEFAULT_RETRY_NUM;

    public int getStep() {
        return step;
    }

    public void setStep(int step) {
        if (step < MIN_STEP || step > MAX_STEP) throw new IllegalArgumentException("步长必须在大于等 : "+ MAX_STEP +",并且小于等于: " + MAX_STEP);
        this.step = step;
    }

    public int getRetryNum() {
        return retryNum;
    }

    public void setRetryNum(int retryNum) {
        if (retryNum <= 0) throw new IllegalArgumentException("重试次数必须大于0");
        this.retryNum = retryNum;
    }

    private String getSelectSql() {
        if (StringUtils.isEmpty(selectSql)) {
            synchronized (mutex) {
                StringBuffer selectSb = new StringBuffer();
                selectSb.append("select " + DEFAULT_COLUMN_SEQ_VALUE + " from " + DEFAULT_TABLE_SEQ_NAME);
                selectSb.append(" where " + DEFAULT_COLUMN_SEQ_NAME + " = ?" );
                this.selectSql = selectSb.toString();
            }
        }
        return selectSql;
    }

    private String getUpdateSql() {
        if (StringUtils.isEmpty(updateSql)) {
            synchronized (mutex) {
                StringBuffer updateSb = new StringBuffer();
                updateSb.append("update " + DEFAULT_TABLE_SEQ_NAME + " set ");
                updateSb.append(DEFAULT_COLUMN_SEQ_VALUE + " = ?");
                updateSb.append(",");
                updateSb.append(DEFAULT_COLUMN_SEQ_UPDATE_TIME + " = ?");
                updateSb.append(" where " + DEFAULT_COLUMN_SEQ_VALUE + " = ?");
                updateSb.append(" and ");
                updateSb.append(DEFAULT_COLUMN_SEQ_NAME + " = ?");

                this.updateSql = updateSb.toString();
            }
        }
        return updateSql;
    }

    public SequenceRange nextRange(String sequenceName) throws SequenceException {
        if (StringUtils.isEmpty(sequenceName)) throw new IllegalArgumentException("序列名不能为空!");

        long oldValue = 0;
        long newValue = 0;

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        for (int i = 1; i <= retryNum; i++) {
            try {
                connection = dataSource.getConnection();
                preparedStatement = connection.prepareStatement(getSelectSql());
                preparedStatement.setString(1,sequenceName);
                resultSet = preparedStatement.getResultSet();
                resultSet.next();
                oldValue = resultSet.getLong(1);

                if (oldValue < 0) throw new SequenceException("序列: " + sequenceName + ", 值不能小于0,请检查数据库sequence配置");

                if (oldValue > Long.MAX_VALUE - DELTA) throw new SequenceException("序列: " + sequenceName + ", 值溢出,超过最大值,请检查数据库sequence配置");

                newValue = buildNewValue(oldValue);
            } catch (SQLException e) {
                logger.error("selectSequence error, SQLException e : {}",e);
                throw new SequenceException(e);
            } catch (Throwable t) {
                logger.error("selectSequence error, Throwable e : {}",t);
                throw new SequenceException(t);
            } finally {
                closeResultSet(resultSet);
                resultSet = null;
                closeStatement(preparedStatement);
                preparedStatement = null;
                closeConnection(connection);
                connection = null;
            }

            try {
                connection = dataSource.getConnection();
                preparedStatement = connection.prepareStatement(getUpdateSql());
                preparedStatement.setLong(1,newValue);
                preparedStatement.setTimestamp(2,new Timestamp(System.currentTimeMillis()));
                preparedStatement.setLong(3,oldValue);
                preparedStatement.setString(4,sequenceName);

                int result = preparedStatement.executeUpdate();
                if (result == 0) continue;

                return buildSequenceRange(oldValue,newValue);
            } catch (SQLException e) {
                logger.error("updateSequence error, SQLException e : {}",e);
                throw new SequenceException(e);
            } catch (Throwable t) {
                logger.error("updateSequence error, Throwable e : {}",t);
                throw new SequenceException(t);
            } finally {
                closeStatement(preparedStatement);
                preparedStatement = null;
                closeConnection(connection);
                connection = null;
            }
        }
        throw new SequenceException("数据库繁忙,请稍后再试!");
    }


    private void closeConnection(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                logger.error("connection 关闭异常, SQLException e: {}", e);
            } catch (Throwable t) {
                logger.error("connection 关闭异常, Throwable e: {}", t);
            }
        }
    }

    private void closeStatement(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                logger.error("statement 关闭异常, SQLException e: {}", e);
            } catch (Throwable t) {
                logger.error("statement 关闭异常, Throwable e: {}", t);
            }
        }
    }

    private void closeResultSet(ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                logger.error("resultSet 关闭异常, SQLException e: {}", e);
            } catch (Throwable t) {
                logger.error("resultSet 关闭异常, Throwable e: {}", t);
            }
        }
    }

    private long buildNewValue(long oldValue) {
        return oldValue + step;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    private SequenceRange buildSequenceRange(long oldValue,long newValue) {
        return new SequenceRange(oldValue + 1,newValue);
    }
}
